//=================================================================================================
// Directional light source.
//=================================================================================================
struct DirectionLight
{
	float4 Ambient;
	float4 Diffuse;
	float4 Specular;

	float3 Direction;
	
	float  Pad;
};


//=================================================================================================
// Point light source.
//=================================================================================================
struct PointLight
{ 
	float4 Ambient;
	float4 Diffuse;
	float4 Specular;

	float3 Position;
	float  Range;
	float3 Att;

	float  Pad;
};


//=================================================================================================
// Spot light source.
//=================================================================================================
struct SpotLight
{
	float4 Ambient;
	float4 Diffuse;
	float4 Specular;

	float3 Position;
	float  Range;
	float3 Direction;
	float  Spot;
	float3 Att;

	float  Pad;
};


//=================================================================================================
// Material information
//=================================================================================================
struct Material
{
	float4 Ambient;
	float4 Diffuse;
	float4 Specular;

	float4 Reflect;
};


//=================================================================================================
// Calculate the directional light
//=================================================================================================
void ComputeDL(Material material, DirectionLight L, float3 normal, float3 toEye, out float4 ambient, out float4 diffuse, out float4 specular)
{
	// Initialize outputs.
	ambient  = float4(0.0f, 0.0f, 0.0f, 0.0f);
	diffuse  = float4(0.0f, 0.0f, 0.0f, 0.0f);
	specular = float4(0.0f, 0.0f, 0.0f, 0.0f);

	// The light vector aims opposite the direction the light rays travel.
	float3 lightVec = -L.Direction;

	// Add ambient term.
	ambient = material.Ambient * L.Ambient;	

	// Add diffuse and specular term, provided the surface is in the line of site of the light.	
	float diffuseFactor = dot(lightVec, normal);

	// Flatten to avoid dynamic branching.
	[flatten]
	if(diffuseFactor >= 0.0f)
	{
		float3 reflectVec = reflect(-lightVec, normal);
		float  specFactor = pow(max(dot(reflectVec, toEye), 0.0f), material.Specular.w);
		
		diffuse  = diffuseFactor * material.Diffuse  * L.Diffuse;
		specular =    specFactor * material.Specular * L.Specular;
	}
}


//=================================================================================================
// Calculate the point light
//=================================================================================================
void ComputePL(Material material, PointLight L, float3 pos, float3 normal, float3 toEye, out float4 ambient, out float4 diffuse, out float4 specular)
{
	// Initialize outputs.
	ambient  = float4(0.0f, 0.0f, 0.0f, 0.0f);
	diffuse  = float4(0.0f, 0.0f, 0.0f, 0.0f);
	specular = float4(0.0f, 0.0f, 0.0f, 0.0f);

	// The vector from the surface to the light.
	float3 lightVec = L.Position - pos;
		
	// The distance from surface to light.
	float d = length(lightVec);
	
	// Range test.
	if(d > L.Range) return;
		
	// Normalize the light vector.
	lightVec /= d; 
	
	// Ambient term.
	ambient = material.Ambient * L.Ambient;	

	// Add diffuse and specular term, provided the surface is in the line of site of the light.
	float diffuseFactor = dot(lightVec, normal);

	// Flatten to avoid dynamic branching.
	[flatten]
	if(diffuseFactor > 0.0f)
	{
		float3 reflectVec = reflect(-lightVec, normal);
		float  specFactor = pow(max(dot(reflectVec, toEye), 0.0f), material.Specular.w);
					
		diffuse  = diffuseFactor * material.Diffuse  * L.Diffuse;
		specular =    specFactor * material.Specular * L.Specular;
	}

	// Attenuate factor.
	float att = 1.0f / dot(L.Att, float3(1.0f, d, d*d));
	diffuse  *= att;
	specular *= att;
}


//=================================================================================================
// Calculate the spot light
//=================================================================================================
void ComputeSL(Material material, SpotLight L, float3 pos, float3 normal, float3 toEye, out float4 ambient, out float4 diffuse, out float4 specular)
{
	// Initialize outputs.
	ambient  = float4(0.0f, 0.0f, 0.0f, 0.0f);
	diffuse  = float4(0.0f, 0.0f, 0.0f, 0.0f);
	specular = float4(0.0f, 0.0f, 0.0f, 0.0f);

	// The vector from the surface to the light.
	float3 lightVec = L.Position - pos;
		
	// The distance from surface to light.
	float d = length(lightVec);
	
	// Range test.
	if(d > L.Range) return;
		
	// Normalize the light vector.
	lightVec /= d; 
	
	// Ambient term.
	ambient = material.Ambient * L.Ambient;	

	// Add diffuse and specular term, provided the surface is in the line of site of the light.
	float diffuseFactor = dot(lightVec, normal);

	// Flatten to avoid dynamic branching.
	[flatten]
	if(diffuseFactor > 0.0f)
	{
		float3 reflectVec = reflect(-lightVec, normal);
		float  specFactor = pow(max(dot(reflectVec, toEye), 0.0f), material.Specular.w);
					
		diffuse  = diffuseFactor * material.Diffuse  * L.Diffuse;
		specular =    specFactor * material.Specular * L.Specular;
	}
	
	// Scale by spotlight factor and attenuate.
	float spot = pow(max(dot(-lightVec, L.Direction), 0.0f), L.Spot);

	// Attenuate factor.
	float att = spot / dot(L.Att, float3(1.0f, d, d*d));
	ambient  *= spot;
	diffuse  *= att;
	specular *= att;
}